// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.ooops.asm;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import com.dustedpixels.asm.utils.ClassAdapters;
import com.dustedpixels.asm.utils.FieldLocalizer;
import com.dustedpixels.asm.utils.MethodInliner;
import com.dustedpixels.common.AdapterSupplier;
import com.dustedpixels.ooops.Inline;
import com.dustedpixels.ooops.FieldsToVariables;

/**
 * Main class adapter for Ooops system.
 * 
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class OoopsClassAdapter extends ClassAdapter {

  private final ClassVisitor out;
  private String className;

  public OoopsClassAdapter(ClassVisitor out) {
    super(new ClassNode());
    this.out = out;
  }

  @Override
  public void visit(int version, int access, String name, String signature, String superName,
      String[] interfaces) {
    this.className = name;
    super.visit(version, access, name, signature, superName, interfaces);
  }

  @Override
  public void visitEnd() {
    super.visitEnd();
    
    ClassNode clazz = (ClassNode) cv;

    // Localize fields.
    ClassNode clazzOut = new ClassNode();
    ClassVisitor out = clazzOut;

    for (Object methodObject : clazz.methods) {
      final MethodNode method = (MethodNode) methodObject;
      if (method.invisibleAnnotations != null) {
        for (Object annotationObject : method.invisibleAnnotations) {
          AnnotationNode annotation = (AnnotationNode) annotationObject;
          if (annotation.desc.equals(Type.getDescriptor(FieldsToVariables.class))) {
            System.out.println("Localize fields: " + method.name);
            out = ClassAdapters.processMethod(out, method.name, method.desc, 
                new AdapterSupplier<MethodVisitor>() {
                  public MethodVisitor get(MethodVisitor out) {
                    return FieldLocalizer.get(out, className, method.desc);
                  }
                });
          }
        }
      }
    }
    if (out != clazzOut) {
      clazz.accept(out);
      clazz = clazzOut;
      clazzOut = new ClassNode();
      out = clazzOut;
    }
    
    // Inline methods
    for (Object methodObject : clazz.methods) {
      final MethodNode method = (MethodNode) methodObject;
      if (method.invisibleAnnotations != null) {
        for (Object annotationObject : method.invisibleAnnotations) {
          AnnotationNode annotation = (AnnotationNode) annotationObject;
          if (annotation.desc.equals(Type.getDescriptor(Inline.class))) {
            System.out.println("Inline: " + method.name);
            out = MethodInliner.inline(out, method);
          }
        }
      }
    }
    if (out != clazzOut) {
      clazz.accept(out);
      clazz = clazzOut;
    }
    
    // Output
    clazz.accept(this.out);
  }
}
